#include "netns.h"
#include "basics/error_handle.h"
#include "limits.h"
#include <net/if.h>
#include <netlink/route/addr.h>
#include <netlink/route/link.h>
#include <netlink/route/link/veth.h>

static void set_link_ipv4(struct nl_sock *sk, const char *link_name,
                          const char *ipv4address) {
    struct rtnl_link *link = NULL;
    struct rtnl_addr *ipv4_addr = NULL;
    struct nl_addr *lo_ipv4_addr = NULL;
    struct nl_cache *link_cache = NULL;
    check(rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache) >= 0);

    int ifindex = 0;

    ipv4_addr = rtnl_addr_alloc();
    lo_ipv4_addr = nl_addr_alloc(8);

    check(nl_addr_parse(ipv4address, AF_INET, &lo_ipv4_addr) >= 0);
    check(rtnl_addr_set_local(ipv4_addr, lo_ipv4_addr) >= 0);

    link = rtnl_link_get_by_name(link_cache, link_name);
    if (link == NULL) {
        sleep(5);
        set_link_ipv4(sk, link_name, ipv4address);
        goto exit;
    }
    check((ifindex = rtnl_link_get_ifindex(link)) != 0);
    rtnl_addr_set_ifindex(ipv4_addr, ifindex);
    rtnl_addr_set_link(ipv4_addr, link);
    check(rtnl_addr_add(sk, ipv4_addr, 0) >= 0);

exit:
    nl_addr_put(lo_ipv4_addr);
    rtnl_addr_put(ipv4_addr);
    rtnl_link_put(link);
    nl_cache_free(link_cache);
}

static void set_link_up(struct nl_sock *sk, const char *link_name) {
    struct rtnl_link *link = NULL;
    struct rtnl_link *new_link = NULL;
    struct nl_cache *link_cache = NULL;

    check((new_link = rtnl_link_alloc()));
    check(rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache) >= 0);

    int ifindex = 0;

    check((link = rtnl_link_get_by_name(link_cache, link_name)) != 0);
    check((ifindex = rtnl_link_get_ifindex(link)) != 0);

    rtnl_link_set_flags(new_link, IFF_UP);

    check(rtnl_link_change(sk, link, new_link, 0) >= 0);

    rtnl_link_put(link);
    rtnl_link_put(new_link);
    nl_cache_free(link_cache);
}

static void create_veth(struct nl_sock *sk, const char *name,
                        const char *peer_name, pid_t pid) {
    struct rtnl_link *link, *peer;
    check((link = rtnl_link_veth_alloc()));
    peer = rtnl_link_veth_get_peer(link);

    rtnl_link_set_name(link, name);
    rtnl_link_set_name(peer, peer_name);

    rtnl_link_set_ns_pid(peer, pid);
    check(rtnl_link_add(sk, link, NLM_F_CREATE) >= 0);

    rtnl_link_put(peer);
    rtnl_link_put(link);
}

static void create_bridge(struct nl_sock *sk, const char *bridge_name) {
    struct rtnl_link *link;

    check((link = rtnl_link_alloc()));
    check(rtnl_link_set_type(link, "bridge") >= 0);

    rtnl_link_set_name(link, bridge_name);
    check(rtnl_link_add(sk, link, NLM_F_CREATE) >= 0);

    rtnl_link_put(link);
}

void delete_link(struct nl_sock *sk, char *link_name) {
    struct rtnl_link *link = NULL;
    struct rtnl_link *new_link = NULL;
    struct nl_cache *link_cache = NULL;

    check((new_link = rtnl_link_alloc()));
    check(rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache) >= 0);

    int ifindex = 0;

    check((link = rtnl_link_get_by_name(link_cache, link_name)) != 0);
    check((ifindex = rtnl_link_get_ifindex(link)) != 0);

    rtnl_link_set_flags(new_link, IFF_UP);

    check(rtnl_link_change(sk, link, new_link, 0) >= 0);

    rtnl_link_put(link);
    rtnl_link_put(new_link);
    nl_cache_free(link_cache);
}

void create_netns_host_bridge() {
    struct nl_sock *sk;
    struct nl_cache *link_cache = NULL;
    struct rtnl_link *bridge;

    sk = nl_socket_alloc();
    check(nl_connect(sk, NETLINK_ROUTE) >= 0);

    check(rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache) >= 0);

    bridge = rtnl_link_get_by_name(link_cache, "neodocker0");
    if (bridge == 0) {
        create_bridge(sk, "neodocker0");
        set_link_ipv4(sk, "neodocker0", "10.0.0.1/24");
        set_link_up(sk, "neodocker0");
        create_iptables_chains();
    }

    rtnl_link_put(bridge);
    nl_cache_free(link_cache);
    nl_socket_free(sk);
}

void create_netns_host_veth(pid_t child_pid, int netns_ip) {
    struct nl_sock *sk;
    struct nl_cache *link_cache = NULL;

    sk = nl_socket_alloc();
    check(nl_connect(sk, NETLINK_ROUTE) >= 0);

    char host_eth_name[10] = {0};
    sprintf(host_eth_name, "pr%d", netns_ip);
    const char *const_host_eth_name = host_eth_name;

    create_veth(sk, const_host_eth_name, "eth0", child_pid);

    struct rtnl_link *bridge, *link;
    check(rtnl_link_alloc_cache(sk, AF_UNSPEC, &link_cache) >= 0);
    check((bridge = rtnl_link_get_by_name(link_cache, "neodocker0")) != 0);
    check((link = rtnl_link_get_by_name(link_cache, const_host_eth_name)) != 0);
    check(rtnl_link_enslave(sk, bridge, link) >= 0);

    set_link_up(sk, const_host_eth_name);

    rtnl_link_put(bridge);
    rtnl_link_put(link);
    nl_cache_free(link_cache);
    nl_socket_free(sk);
}

void create_netns_container(int netns_ip) {
    struct nl_sock *sk;

    sk = nl_socket_alloc();
    check(nl_connect(sk, NETLINK_ROUTE) >= 0);
    char ip[20] = {0};
    sprintf(ip, "10.0.0.%d/24", netns_ip);
    const char *const_ip = ip;

    set_link_ipv4(sk, "eth0", const_ip);
    set_link_up(sk, "eth0");
    set_link_up(sk, "lo");

    nl_socket_free(sk);
}

void create_iptables_chains() {
    const char *commands[4] = {
        "iptables -A FORWARD -o neodocker0 -j ACCEPT",
        "iptables -A FORWARD -i neodocker0 -j ACCEPT",
        "iptables -t nat -A POSTROUTING -s 10.0.0.0/24 ! -o "
        "neodocker0 -j MASQUERADE",
        "echo 1 > /proc/sys/net/ipv4/ip_forward"};
    for (int i = 0; i < 4; i++) {
        check(system(commands[i]) == 0);
    }
}

void create_container_dns(const char *container_path) {
    char command[2 * PATH_MAX];
    sprintf(command, "cp /etc/resolv.conf %s/etc/resolv.conf", container_path);
    check(system(command) == 0);
}
